package com.juyu.miliao.api.service;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.juyu.miliao.api.constant.PayContentType;
import com.juyu.miliao.api.constant.PayType;
import com.juyu.miliao.api.constant.WeiXinTradeType;
import com.juyu.miliao.api.domain.*;
import com.juyu.miliao.api.domain.dto.DiscountInfoDTO;
import com.juyu.miliao.api.domain.dto.PreferentialInformationTipsDTO;
import com.juyu.miliao.api.util.*;
import com.juyu.miliao.api.util.pay.ZfbPayUtil;
import com.juyu.miliao.common.constant.EnumType;
import com.juyu.miliao.common.constant.PackageGroupConstant;
import com.juyu.miliao.common.domain.ConfigApp;
import com.juyu.miliao.common.domain.ConfigWxPay;
import com.juyu.miliao.common.exception.ConsumeRuntimeException;
import com.juyu.miliao.common.exception.IllegalParamException;
import com.juyu.miliao.common.util.DoubleFormatUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * H5计费service
 *
 * @author xuanqicheng
 * @create 2018-11-9:27 AM
 */
@Service
public class H5PayService {
    /**
     * 微信统一下单接口
     */
    private static final String WEI_XIN_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private final Logger payNotifyLogger = LoggerFactory.getLogger("payNotify");
    @Resource
    private CoinPriceService coinPriceService;
    @Resource
    private VipService vipService;
    @Resource
    private ConfigAppService configAppService;
    @Resource
    private OrderService orderService;
    @Resource
    private ConfigWxPayService configWxPayService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private BuyLimitService buyLimitService;
    @Resource
    private ObjectMapper objectMapper;

    /**
     * 支付
     *
     * @param h5PayInfo
     * @return
     */
    public Object getH5Pay(H5PayInfo h5PayInfo) {

        checkParameter(h5PayInfo);

        //获取支付价格
        PayShowInfo payShowInfo = getPaymentPrice(h5PayInfo.getContentType(), h5PayInfo.getContentId());

        double fee = payShowInfo.getPrice();
        if (h5PayInfo.getContentType() == PayContentType.PAY_CONTENT_TYPE_VIP.getCode() ||
                h5PayInfo.getContentType() == PayContentType.PAY_SVIP_TYPE_VIP.getCode()
                || h5PayInfo.getContentType() == PayContentType.PAY_PLATINUM_VIP.getCode()) {
            fee = fee * h5PayInfo.getPayNum();
        } else if(h5PayInfo.getContentType() == PayContentType.PAY_BUY_LIMIT.getCode()) {

        }
        if (StringUtils.isBlank(h5PayInfo.getKey())) {
            if (payShowInfo.getBoolRandom() == 1 && payShowInfo.getRandomPrice() > 0) {
                double discountFee = DoubleFormatUtil.randomDouble(payShowInfo.getRandomPrice());
                fee = fee - discountFee;
                payShowInfo.setPrice(fee);
                payShowInfo.setDiscountFee(new BigDecimal(discountFee + ""));
            } else {
                payShowInfo.setPrice(fee);
            }
        } else {
            BigDecimal feeBigdecimal = new BigDecimal(fee + "");

            BigDecimal bigDecimal;

            DiscountInfoDTO discountInfoDTO = getDiscountInfoDTO(h5PayInfo.getKey(), h5PayInfo.getUserId());

            if (h5PayInfo.getContentType() == PayContentType.PAY_CONTENT_TYPE_GOLD.getCode()) {
                bigDecimal = discountInfoDTO.getCoinDiscount();
            } else {
                bigDecimal = discountInfoDTO.getVipDiscount();
            }

            BigDecimal discountFeeBigDecimal = feeBigdecimal.multiply(bigDecimal);

            payShowInfo.setPrice(feeBigdecimal.subtract(discountFeeBigDecimal).doubleValue());

            payShowInfo.setSalesmanId(discountInfoDTO.getFromUserId());

            payShowInfo.setDiscountFee(discountFeeBigDecimal);
        }


        // 透传参数
        String customParameter = getCustomParameter(h5PayInfo);


        ConfigWxPay configWxPay = configWxPayService
                .getConfigWxPayByAppPackageIdAndPhoneSystemAndType(h5PayInfo.getAppPackageId(),
                        h5PayInfo.getPhoneSystem(), h5PayInfo.getTradeType());

        if (configWxPay == null) {
            throw new ConsumeRuntimeException("支付通道配置参数不正确，请检查参数");
        }

        if (h5PayInfo.getPayType() == PayType.PAY_TYPE_ZHI_FU_BO.getCode()) {
            return h5ZhiFuBoPay(h5PayInfo, payShowInfo, customParameter, configWxPay);
        } else {
            JSONObject result = h5WeiXinPay(h5PayInfo, payShowInfo, customParameter, configWxPay);
            return result;
        }

    }

    /**
     * 获取优惠信息
     *
     * @param key      销售优惠key
     * @param inUserId 优惠人id
     * @return
     */
    private DiscountInfoDTO getDiscountInfoDTO(String key, Long inUserId) {
        String target = stringRedisTemplate.opsForValue().get(key);
        payNotifyLogger.info(key + ":" + target);
        if (StringUtils.isBlank(target)) {
            throw new ConsumeRuntimeException("优惠链接已经过期，请联系销售重新获取！");
        }

        DiscountInfoDTO discountInfoDTO = JSONObject.parseObject(target, DiscountInfoDTO.class);
        if (!discountInfoDTO.getToUserId().equals(inUserId)) {
            throw new ConsumeRuntimeException("接受者ID不正确");
        }
        return discountInfoDTO;
    }

    private Object h5ZhiFuBoPay(H5PayInfo h5PayInfo, PayShowInfo payShowInfo, String customParameter,
                                ConfigWxPay configWxPay) {
        String passbackParams = "";
        try {
            passbackParams = URLEncoder.encode(customParameter, "GBK");
        } catch (Exception e) {
            if (payNotifyLogger.isErrorEnabled()) {
                payNotifyLogger.error("下单透传参数encode出错", e);
            }
        }

        String outTradeNo = getOutTradeNo();

        ConfigApp configApp = configAppService.getConfigApp(h5PayInfo.getAppPackageId(),
                h5PayInfo.getPhoneSystem().toString());

        /**
         * 是否是线上状态
         */
        Integer online = getOnline(configApp);

        AlipayTradeWapPayResponse response = ZfbPayUtil.aliWapPay(payShowInfo.getShowPrice(), payShowInfo.getShowDesc(),
                outTradeNo, payShowInfo.getPrice() + "", passbackParams);
        if (response.isSuccess()) {
            // 下单成功 初始化订单
            getOrderInfo(h5PayInfo, payShowInfo.getPrice(), payShowInfo.getDiscountFee(), payShowInfo.getSalesmanId(),
                    configApp, online, outTradeNo, configWxPay.getId(), payShowInfo.getExt());
            String temp = response.getBody();
            Map<String, Object> map = new HashMap<>(3);
            map.put("code", 200);
            map.put("message", "下单成功");
            map.put("wapCode", temp);
            return map;
        } else {
            Map<String, Object> map = new HashMap<>(2);
            map.put("code", 300);
            map.put("message", "下单失败");
            return map;
        }


    }

    /**
     * 微信H5 支付逻辑
     *
     * @param h5PayInfo
     * @param payShowInfo
     * @param customParameter 自定义参数
     */
    private JSONObject h5WeiXinPay(H5PayInfo h5PayInfo, PayShowInfo payShowInfo, String customParameter,
                                   ConfigWxPay configWxPay) {

        ConfigApp configApp = configAppService.getConfigApp(h5PayInfo.getAppPackageId(),
                h5PayInfo.getPhoneSystem().toString());
        /**
         * 是否是线上状态
         */
        Integer online = getOnline(configApp);

        String tradeType = "";

        //交易类型 1: MWEB--H5支付，2：JSAPI--JSAPI(公众号号)，3：JSAPI--JSAPI(小程序)，4：NATIVE--Native支付
        if (h5PayInfo.getTradeType() == 1) {
            tradeType = WeiXinTradeType.WEI_XIN_TRADE_TYPE_H5.getTradeType();
        } else if (h5PayInfo.getTradeType() == 2 || h5PayInfo.getTradeType() == 3) {
            tradeType = WeiXinTradeType.WEI_XIN_TRADE_TYPE_JS.getTradeType();
        } else if (h5PayInfo.getTradeType() == 4) {
            tradeType = WeiXinTradeType.WEI_XIN_TRADE_TYPE_NATIVE.getTradeType();
        } else {
            throw new ConsumeRuntimeException("支付类型不正确");
        }

        try {

            // 商品描述
            String body = payShowInfo.getShowDesc();

            String feeStr = String.valueOf((payShowInfo.getPrice()) * 100);

            // 总金额 单位为分
            String totalFee = feeStr.substring(0, feeStr.indexOf("."));


            String outTradeNo = getOutTradeNo();

            // 用户端实际ip
            String spbillCreateIp = h5PayInfo.getIpAddress();

            Map<String, String> wxMap = getWeiXinPayParameter(h5PayInfo, customParameter, configWxPay, body, totalFee,
                    spbillCreateIp, outTradeNo, tradeType);
            payNotifyLogger.info("H5微信支付请求参数：{}", wxMap);


            //进行下单
            Map<String, String> resultMap = getWeChatToOrder(wxMap);
            payNotifyLogger.info("H5微信支付下单返还：{}", resultMap);
            String returnCode = resultMap.get("return_code");

            String resultCode = resultMap.get("result_code");

            if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {

                // 下单成功 初始化订单
                getOrderInfo(h5PayInfo, payShowInfo.getPrice(), payShowInfo.getDiscountFee(), payShowInfo.getSalesmanId(),
                        configApp, online, outTradeNo, configWxPay.getId(), payShowInfo.getExt());

                JSONObject resultJson = new JSONObject();
                resultJson.put("code", 200);
                resultJson.put("message", "下单成功");
                if (tradeType.equals(WeiXinTradeType.WEI_XIN_TRADE_TYPE_H5.getTradeType())) {
                    String mwebUrl = resultMap.get("mweb_url");
                    String redirectUrl = "";
                    String payUrl = mwebUrl + "&redirect_url=" + URLEncoder.encode(redirectUrl, "UTF-8");
                    resultJson.put("payUrl", payUrl);
                } else {
                    Map<String, String> map = new HashMap<>(0);
                    map.put("appId", resultMap.get("appid"));
                    Long second = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
                    map.put("timeStamp", second + "");
                    map.put("nonceStr", resultMap.get("nonce_str"));
                    map.put("package", "prepay_id=" + resultMap.get("prepay_id"));
                    map.put("signType", "MD5");
                    String sign = Md5Util.signGFWXPayDataMD5(map, configWxPay.getKey());
                    map.put("paySign", sign);
                    resultJson.put("payUrl", map);

                }

                return resultJson;
            } else {
                payNotifyLogger.error("resultMap={},appId={}", resultMap.toString(), configWxPay.getAppid());

                JSONObject resultJson = new JSONObject();
                resultJson.put("code", 300);
                resultJson.put("message", "下单失败");
                return resultJson;

            }

        } catch (Exception ex) {
            payNotifyLogger.error("微信支付下单异常： ", ex);
            JSONObject resultJson = new JSONObject();
            resultJson.put("code", 300);
            resultJson.put("message", "下单失败");
            return resultJson;
        }

    }

    /**
     * 生成订单号
     *
     * @param h5PayInfo
     * @param fee         支付金额
     * @param discountFee 优惠价格
     * @param salesmanId  销售id
     * @param configApp
     * @param online
     * @param outTradeNo
     * @param configPayId
     */
    private void getOrderInfo(H5PayInfo h5PayInfo, double fee, BigDecimal discountFee, Long salesmanId, ConfigApp configApp,
                              Integer online, String outTradeNo, int configPayId, String ext) {
        Order order = new Order();
        order.setUserId(h5PayInfo.getUserId());
        order.setContentType(h5PayInfo.getContentType());
        order.setFee(fee);
        order.setOrderId(outTradeNo);
        order.setOrderStatus(2);
        if (discountFee != null) {
            order.setDiscountFee(discountFee);
        }
        order.setPayType(h5PayInfo.getPayType());
        order.setContentId(h5PayInfo.getContentId());
        order.setChannelId(h5PayInfo.getChannel());
        order.setVersionNum(h5PayInfo.getVersionNum());
        order.setPhoneSystem(h5PayInfo.getPhoneSystem());
        order.setGroupType(configApp.getGroupType());
        order.setAppPackageId(h5PayInfo.getAppPackageId());
        order.setOnline(online);
        order.setOutId(outTradeNo);
        order.setPayNum(h5PayInfo.getPayNum());
        order.setConfigPayId(configPayId + "");
        if (salesmanId != null) {
            order.setSalesmanId(salesmanId);
        }
        order.setRobotOrderType(h5PayInfo.getRobotOrderType());
        order.setH5OrderType(h5PayInfo.getH5OrderType());
        order.setExt(ext);
        orderService.insertOrder(order);

    }

    /**
     * 微信下单
     *
     * @param wxMap
     * @return
     * @throws Exception
     */
    private Map<String, String> getWeChatToOrder(Map<String, String> wxMap) throws Exception {
        String payXml = XmlUtil.payInfoToXML(wxMap);
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(WEI_XIN_PAY_URL);
        httpPost.setEntity(new StringEntity(payXml, "utf-8"));
        CloseableHttpResponse payResponse = httpclient.execute(httpPost);

        //下订单 返回结果数据
        HttpEntity entity = payResponse.getEntity();

        String result = EntityUtils.toString(entity, "utf-8");

        EntityUtils.consume(entity);

        return XmlUtil.parseXml(result);
    }

    /**
     * 设置微信支付请求参数
     *
     * @param h5PayInfo
     * @param customParameter
     * @param body
     * @param totalFee
     * @param spbillCreateIp
     * @param outTradeNo
     * @param tradeType
     * @return
     */
    private Map<String, String> getWeiXinPayParameter(H5PayInfo h5PayInfo, String customParameter, ConfigWxPay configWxPay, String body,
                                                      String totalFee, String spbillCreateIp, String outTradeNo,
                                                      String tradeType) {
        Map<String, String> wxMap = new HashMap<String, String>(0);
        //公众账号ID
        wxMap.put("appid", configWxPay.getAppid());
        //商户号
        wxMap.put("mch_id", configWxPay.getMchId());
        //随机字符串
        wxMap.put("nonce_str", getOutTradeNo());
        //商品描述
        wxMap.put("body", body);
        //商户订单号
        wxMap.put("out_trade_no", outTradeNo);
        //标价金额
        wxMap.put("total_fee", totalFee + "");
        //终端IP
        wxMap.put("spbill_create_ip", spbillCreateIp);
        //通知地址
        wxMap.put("notify_url", configWxPay.getNotifyUrl());
        //交易类型
        wxMap.put("trade_type", tradeType);
        if (tradeType.equals(WeiXinTradeType.WEI_XIN_TRADE_TYPE_JS.getTradeType())) {
            wxMap.put("openid", h5PayInfo.getOpenId());
        }
        // 附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
        wxMap.put("attach", customParameter);
        //签名
        String sign = Md5Util.signGFWXPayDataMD5(wxMap, configWxPay.getKey());
        wxMap.put("sign", sign);
        return wxMap;
    }

    /**
     * 获取是否线上状态
     *
     * @param configApp
     * @return
     */
    private Integer getOnline(ConfigApp configApp) {
        Integer online = null;
        if (configApp.getPackageGroup().equals(PackageGroupConstant.VEST)) {
            online = EnumType.VEST.state;
        } else {
            online = EnumType.ONLINE.state;
        }
        return online;
    }


    /**
     * 获取自定义参数
     *
     * @param h5PayInfo
     * @return
     */
    private String getCustomParameter(H5PayInfo h5PayInfo) {
        ObjectMapper mapper = new ObjectMapper();
        String customParameter = "";

        Map<String, String> params = new HashMap<String, String>(6);
        params.put("contentType", h5PayInfo.getContentType() + "");
        params.put("contentId", h5PayInfo.getContentId() + "");
        params.put("userId", h5PayInfo.getUserId() + "");
        params.put("appPackageId", h5PayInfo.getAppPackageId());
        params.put("phoneSystem", h5PayInfo.getPhoneSystem() + "");
        params.put("wxPayType", h5PayInfo.getTradeType() + "");

        try {
            customParameter = mapper.writeValueAsString(params);
            if (h5PayInfo.getPayType() == PayType.PAY_TYPE_ZHI_FU_BO.getCode()) {
                customParameter = URLEncoder.encode(customParameter, "GBK");
            }
        } catch (Exception ex) {
            if (payNotifyLogger.isErrorEnabled()) {
                payNotifyLogger.error("下单透传参数出错", ex);
            }
        }
        return customParameter;
    }

    /**
     * 根据计费类型与计费内容获取支付价格
     *
     * @param contentType 计费类型：1金币 2VIP
     * @param contentId   计费内容查询id
     * @return
     */
    private PayShowInfo getPaymentPrice(Integer contentType, Integer contentId) {
        PayShowInfo payShowInfo = new PayShowInfo();
        //购买金币
        if (contentType == PayContentType.PAY_CONTENT_TYPE_GOLD.getCode()) {
            CoinPrice coinPrice = coinPriceService.getCoinPriceById(contentId);
            if (coinPrice == null) {
                throw new ConsumeRuntimeException("金币计费内容错误");
            }
            String showDesc = "";
            if (coinPrice.getCoinType() == 8) {
                showDesc = coinPrice.getShowPrice().split("#")[0];
            } else {
                showDesc = coinPrice.getShowPrice();
            }
            String desc = "寻寻充值(" + coinPrice.getShowCoin() + ")";
            payShowInfo.setShowDesc(desc);
            payShowInfo.setShowPrice(showDesc);
            payShowInfo.setPrice(coinPrice.getPrice());
            payShowInfo.setBoolRandom(coinPrice.getBoolRandom());
            payShowInfo.setRandomPrice(coinPrice.getRandomPrice());
        }
        //购买vip
        else if (contentType == PayContentType.PAY_CONTENT_TYPE_VIP.getCode() ||
                contentType == PayContentType.PAY_SVIP_TYPE_VIP.getCode()) {
            Vip vip = vipService.getVipById(contentId);
            if (vip == null) {
                throw new IllegalParamException("VIP计费内容错误");
            }
            String desc = "购买寻寻" + vip.getShowDesc();
            double fee = vip.getPrice();
            String showPrice = vip.getShowPrice();
            payShowInfo.setShowDesc(desc);
            payShowInfo.setShowPrice(showPrice);
            payShowInfo.setPrice(fee);
        }
        //限时抢购
        else {
            List<BuyLimit> buyLimitList = buyLimitService.listBuyLimit(LocalDateTime.now(), 1);
            if(buyLimitList.isEmpty()) {
                throw new IllegalParamException("限时抢购未开始");
            }
            BuyLimit buyLimit = buyLimitList.get(0);
            payShowInfo.setShowDesc("限时抢购");
            payShowInfo.setPrice(buyLimit.getDiscountPrice().doubleValue());
            payShowInfo.setDiscountFee(buyLimit.getDiscountPrice());
            ObjectNode json = objectMapper.createObjectNode()
                    .put("id", buyLimit.getId())
                    .put("beans", buyLimit.getBeans())
                    .put("energy", buyLimit.getEnergy());
            payShowInfo.setExt(json.toString());
        }
        return payShowInfo;
    }

    /**
     * 商户订单号，商户网站订单系统中唯一订单号
     *
     * @return
     */
    private String getOutTradeNo() {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
        String localTime = dateTimeFormatter.format(LocalDateTime.now());
        return ComputeRedPacketUtil.getRandom(100, 999) + localTime;
    }

    /**
     * 校验参数
     *
     * @param h5PayInfo
     */
    private void checkParameter(H5PayInfo h5PayInfo) {

        if (h5PayInfo.getPayType() != PayType.PAY_TYPE_ZHI_FU_BO.getCode()
                && h5PayInfo.getPayType() != PayType.PAY_TYPE_WEI_XIN.getCode()) {
            throw new ConsumeRuntimeException("无此支付类型");
        }

        if (h5PayInfo.getContentType() != PayContentType.PAY_CONTENT_TYPE_GOLD.getCode()
                && h5PayInfo.getContentType() != PayContentType.PAY_CONTENT_TYPE_VIP.getCode()
                && h5PayInfo.getContentType() != PayContentType.PAY_SVIP_TYPE_VIP.getCode()
                && h5PayInfo.getContentType() != PayContentType.PAY_BUY_LIMIT.getCode()) {

            throw new ConsumeRuntimeException("无此计费类型");
        }


    }

    public Object getPreferentialInformationTips(PreferentialInformationTipsDTO tipsDTO) {

        DiscountInfoDTO discountInfoDTO = getDiscountInfoDTO(tipsDTO.getKey(), tipsDTO.getUserId());

        if (tipsDTO.getContentType() == PayContentType.PAY_CONTENT_TYPE_GOLD.getCode()) {
            CoinPrice coinPrice = coinPriceService.getCoinPriceById(tipsDTO.getContentId());
            if (coinPrice == null) {
                throw new ConsumeRuntimeException("金币计费内容错误");
            }
            //原价
            BigDecimal fee = new BigDecimal(coinPrice.getPrice() + "");
            BigDecimal bigDecimal = discountInfoDTO.getCoinDiscount();
            //优惠金额
            BigDecimal discountFee = fee.multiply(bigDecimal);
            //实际支付价格
            BigDecimal payFee = fee.subtract(discountFee);
            //优惠百分比
            int discountPoint = (int) (bigDecimal.doubleValue() * 100);
            String message = "温馨提示：您要充值" + coinPrice.getPrice() + "元，获得金币" + coinPrice.getCoinNumber() + "个，实际只花费"
                    + payFee + "元，优惠" + discountPoint + "%。";
            return message;
        } else {

            Vip vip = vipService.getVipById(tipsDTO.getContentId());
            if (vip == null) {
                throw new IllegalParamException("VIP计费内容错误");
            }
            //原价
            BigDecimal fee = new BigDecimal(vip.getPrice() + "");

            BigDecimal bigDecimal = discountInfoDTO.getVipDiscount();
            //优惠金额
            BigDecimal discountFee = fee.multiply(bigDecimal);
            //实际支付价格
            BigDecimal payFee = fee.subtract(discountFee);
            //优惠百分比
            int discountPoint = (int) (bigDecimal.doubleValue() * 100);

            String message = "温馨提示：您要充值" + vip.getPrice() + "元会员，实际只花费" + payFee.doubleValue() +
                    "元，优惠" + discountPoint + "%。";

            return message;
        }

    }


}
